﻿package ygg.printer
{
	import j.sus2maj7.printer.*;
	import j.sus2maj7.context.*;
	import j.sus2maj7.util.*;
	import flash.utils.ByteArray;
	
	import ygg.encoding.swf.abc.*;
	import ygg.encoding.swf.abc.object.*;
	import ygg.encoding.swf.abc.constant.*;
	
	public class MethodPrinter extends TraitPrinter
	{
		
		public function MethodPrinter()
		{
			super();
		}
		override public function printToString(o:*, context:IContext=null):String
		{
			return printToStringB(o, context).toString();
		}
		override public function printToStringB(o:*, context:IContext=null):StringBuilder
		{
			return print(Method(o), AbcContext(context.vars.abcContext), PrinterContext(context) );
		}
		private function format(mi:Method):String
		{
			var s:String = "";
			
			if (mi.isNative) s += "native ";
			if (mi.isStatic ) s += 'static ';
			if (mi.isOverride ) s += 'override ';
			if (mi.accessSpecifier.length) s += mi.accessSpecifier + ' ';
			//s += (mi.name) ? 'private ' : 'public ');
			s += 'function ';
			if (mi.isGetter ) s += 'get ';
			if (mi.isSetter ) s += 'set ';
			
			s += mi.name + "(";
			
			var i:int = 0;
			var len:int = (mi.params) ? mi.params.length : 0;
			for (i; i < len; i++)
			{
				if (i > 0) s += ', ';
				else s += ' ';
				
				if (mi.hasParamNames)
				{
					if (mi.params[i].name != null)
						s += mi.params[i].name ;
					else s += 'param_' + i;
				}
				else
				{
					if (mi.isSetter) s += 'value';
					else s += 'param_' + i;
				}
				
				s +=  ':' + mi.params[i].type;
				
				if (mi.hasOptional)
				{
					if (mi.params[i].value != undefined) s += '=' + mi.params[i].value;
				}
			}
			
			if (mi.needsRest)
			{
				if (i > 0) s += ',';
				s += ' ...args ';
			}
			else if (i > 0) s += ' ';
			
			s += ")";
			if (!mi.isConstructor) s+= ':' + mi.returnType;
			
			return s
		}
		private function print(mi:Method, context:AbcContext, pc:PrinterContext):StringBuilder
		{
			//trace('mip print');
			var cPool:ConstantPool = context.constantPool;
			var sb:StringBuilder = (pc.sb == null) ? new StringBuilder() : pc.sb;
			
			var indent:String = pc.indent || '';
			var tab:String = pc.tab || '    ';
			
			if (mi.metadata)
			{
				sb.append("\n");
				for each (var md:* in mi.metadata)
				{
					sb.append( md + "\n" + indent)
				}
			}
			
			sb.append(format(mi));
			sb.append(" // disp_id " + mi.id + ' ' + mi.needsArguments + ' ' + mi.needsActivation+ ' ' + mi.needsRest+ ' ' + mi.setDxns);
			
			var code:AbcBinary;
			if (mi.body) code = mi.body.binary as AbcBinary;
			var oldIndent:String = indent;
			if (code != null)
			{
				sb.append("\n"+indent + "{\n");
				
				
				indent += tab;
				
				if (mi.needsActivation)
				{
					sb.append(indent + "activation\n" + indent +"{\n" + indent + tab );
					var ipf:IPrinterFactory = pc.vars.factory || new AbcPrinterFactory();
					var ip:IPrinter;
					
					pc.indent = indent + tab;
					for each (var t:Trait in mi.body.activation.members)
					{
						if (t)
						{
							ip = ipf.getPrinter(t);
							sb.append( ip.printToString(t, pc) );
						}
					}
					pc.indent = indent;
					sb.append("\n" + indent + "}\n");
				}
				
				sb.append(indent + "// numLocals= " + mi.body.numLocals +
					  "    initScope= " + mi.body.initScopeDepth +
					  "    maxScope= " + mi.body.maxScopeDepth +
					  "    maxStack= " + mi.body.maxStack +
					  "    codeLen= " + code.length +"\n");
				sb.append(indent + "// numExceptions= " + mi.body.exceptions.length + "\n");
				
				code.position = 0
				
				var labels:Label_ = new Label_(); //?
				
				while (code.bytesAvailable > 0)
				{
					var s:String = code.position.toString();
					
					while (s.length < 8)
					{
						s += ' ';
					}
					s = indent + s;
					var opcode:uint = code.readUnsignedByte()

					if (opcode == OpCode.OP_label || ((code.position - 1) in labels))
					{
						sb.append(indent + "\n");
						sb.append(indent + labels.labelFor(code.position - 1) + ": \n");
					}

					s += OpCode.opNames[opcode];
					
					s += OpCode.opNames[opcode].length < 8 ? "\t\t" : "\t";
						
					switch(opcode)
					{
						case OpCode.OP_debugfile:
						case OpCode.OP_pushstring:
							s += '"' + cPool.strings[code.readEncU32()].replace(/\n/g, "\\n").replace(/\t/g, "\\t") + '"';
							break
							
						case OpCode.OP_pushnamespace:
							s += cPool.namespaces[code.readEncU32()];
							break
							
						case OpCode.OP_pushint:
							var i:int = cPool.ints[code.readEncU32()];
							s += i + "\t// 0x" + i.toString(16);
							break
							
						case OpCode.OP_pushuint:
							var u:uint = cPool.uints[code.readEncU32()];
							s += u + "\t// 0x" + u.toString(16);
							break;
							
						case OpCode.OP_pushdouble:
							s += cPool.doubles[code.readEncU32()];
							break;
							
						case OpCode.OP_getsuper:
						case OpCode.OP_setsuper:
						case OpCode.OP_getproperty:
						case OpCode.OP_initproperty:
						case OpCode.OP_setproperty:
						case OpCode.OP_getlex:
						case OpCode.OP_findpropstrict:
						case OpCode.OP_findproperty:
						case OpCode.OP_finddef:
						case OpCode.OP_deleteproperty:
						case OpCode.OP_istype:
						case OpCode.OP_coerce:
						case OpCode.OP_astype:
						case OpCode.OP_getdescendants:
							s += cPool.multinames[code.readEncU32()]; //check for imports
							break;
							
						case OpCode.OP_constructprop:
						case OpCode.OP_callproperty:
						case OpCode.OP_callproplex:
						case OpCode.OP_callsuper:
						case OpCode.OP_callsupervoid:
						case OpCode.OP_callpropvoid:
							s += cPool.multinames[code.readEncU32()]
							s += " (" + code.readEncU32() + ")";
							break;
							
						case OpCode.OP_newfunction:
						{
							var method_id:int = code.readEncU32();
							s += context.methods[method_id];
							context.methods[method_id].anon = true; //anon
							break;
						}
						
						case OpCode.OP_callstatic:
							s += context.methods[code.readEncU32()];
							s += " (" + code.readEncU32() + ")";
							break;
							
						case OpCode.OP_newclass:
							s += context.instances[code.readEncU32()]
							break;
							
						case OpCode.OP_lookupswitch:
							var pos:uint = code.position-1;
							var target:int = pos + code.readS24();
							var maxindex:int = code.readEncU32();
							s += "default:" + labels.labelFor(target) //// target + "("+(target-pos)+")"
							s += " maxcase:" + maxindex
							for (var j:int = 0; j <= maxindex; j++)
							{
								target = pos + code.readS24();
								s += " " + labels.labelFor(target) //// target + "("+(target-pos)+")"
							}
							break;
							
						case OpCode.OP_jump:
						case OpCode.OP_iftrue:
						case OpCode.OP_iffalse:
						case OpCode.OP_ifeq:
						case OpCode.OP_ifne:
						case OpCode.OP_ifge:
						case OpCode.OP_ifnge:
						case OpCode.OP_ifgt:
						case OpCode.OP_ifngt:
						case OpCode.OP_ifle:
						case OpCode.OP_ifnle:
						case OpCode.OP_iflt:
						case OpCode.OP_ifnlt:
						case OpCode.OP_ifstricteq:
						case OpCode.OP_ifstrictne:
							var offset:int = code.readS24();
							var target2:int = code.position + offset;
							////s += target2 + " ("+offset+")"
							s += labels.labelFor(target2)
							//if (!((code.position) in labels))
							//	s += "\n"
							break;
							
						case OpCode.OP_inclocal:
						case OpCode.OP_declocal:
						case OpCode.OP_inclocal_i:
						case OpCode.OP_declocal_i:
						case OpCode.OP_getlocal:
						case OpCode.OP_kill:
						case OpCode.OP_setlocal:
						case OpCode.OP_debugline:
						case OpCode.OP_getglobalslot:
						case OpCode.OP_getslot:
						case OpCode.OP_setglobalslot:
						case OpCode.OP_setslot:
						case OpCode.OP_pushshort:
						case OpCode.OP_newcatch:
							s += code.readEncU32();
							break
							
						case OpCode.OP_debug:
							s += code.readUnsignedByte()
							s += " " + code.readEncU32()
							s += " " + code.readUnsignedByte()
							s += " " + code.readEncU32()
							break;
							
						case OpCode.OP_newobject:
							s += "{" + code.readEncU32() + "}";
							break;
							
						case OpCode.OP_newarray:
							s += "[" + code.readEncU32() + "]";
							break;
							
						case OpCode.OP_call:
						case OpCode.OP_construct:
						case OpCode.OP_constructsuper:
							s += "(" + code.readEncU32() + ")";
							break;
						case OpCode.OP_pushbyte:
						case OpCode.OP_getscopeobject:
							s += code.readByte();
							break;
							
						case OpCode.OP_hasnext2:
							s += code.readEncU32() + " " + code.readEncU32();
							
						default:
							////if (OpCode.opNames[opcode] == ("0x"+opcode.toString(16).toUpperCase()))
							////	s += " UNKNOWN OPCODE"
							break
					}
					
					//var size:int = code.position - start
					
					
					//OpCode.totalSize += size
					//OpCode.opSizes[opcode] = int(OpCode.opSizes[opcode]) + size;
					
					sb.append(s + "\n");
				}
				
				sb.append(oldIndent + "}\n" + oldIndent);
				
			}
			else
			{
				sb.append(";\n" + oldIndent);
			}
			sb.append("\n" + oldIndent);
			pc.indent = oldIndent;
			return sb;
		}
	}
}